﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using static System.Net.Mime.MediaTypeNames;

namespace Utils.Helper
{
    public class Retrier
    {
        private int[] retries;

        private readonly Queue<RetryData> retryDatas = new Queue<RetryData>();

        private readonly Timer timer;
        public Retrier(params int[] retries)
        {
            this.retries = retries;
            timer = new Timer(TimerCall, this, 1000, 1000);
        }

        private void TimerCall(object obj)
        {
            try
            {
                List<RetryData> next = new List<RetryData>();
                while (retryDatas.Count != 0)
                {
                    RetryData retryData = retryDatas.Dequeue();
                    if (retryData.RetryTime > DateTimeOffset.Now.ToUnixTimeMilliseconds())
                    {
                        // 未到重试时间
                        next.Add(retryData);
                        continue;
                    }
                    if (!retryData.Invoke(retries))
                    {
                        next.Add(retryData);
                    }
                }
                next.ForEach(retryData => retryDatas.Enqueue(retryData));
            }
            catch (Exception e)
            {

            }
        }

        public void Do(Action runnable, Action<Exception> exceptionConsumer)
        {
            RetryData retryData = new RetryData(runnable, exceptionConsumer);
            if (!retryData.Invoke(retries))
            {
                retryDatas.Enqueue(retryData);
            }
        }

        class RetryData
        {
            private readonly Action runnable;

            private readonly Action<Exception> exceptionConsumer;

            public int RetryCount { get; private set; }

            public long RetryTime { get; private set; }

            public RetryData(Action runnable, Action<Exception> exceptionConsumer)
            {
                RetryCount = 0;
                this.runnable = runnable;
                this.exceptionConsumer = exceptionConsumer;
            }

            public bool Invoke(int[] retries)
            {
                try
                {
                    runnable.Invoke();
                    return true;
                }
                catch (Exception e)
                {
                    exceptionConsumer.Invoke(e);
                    if (NextTime(retries))
                    {
                        return false;
                    }
                }
                return true;
            }

            public bool NextTime(int[] retries)
            {
                if (retries.Length > RetryCount)
                {
                    RetryTime = DateTimeOffset.Now.ToUnixTimeMilliseconds() + retries[RetryCount] * 1000;
                    RetryCount++;
                    return true;
                }
                return false;
            }
        }
    }
}
